import { existsSync, globSync } from "node:fs"
import { homedir } from "os"
import puppeteer from "puppeteer-core"
import { fileURLToPath } from "url"

/**
 * @import {Browser, Page} from 'puppeteer-core'
 */

export function getExecutablePath() {
    let chrome1 = 'C:/Program Files/Google/Chrome/Application/chrome.exe'
    let chrome2 = `C:/Program Files (x86)/Google/Chrome/Application/chrome.exe`
    if (existsSync(chrome1)) {
        return chrome1
    }
    if (existsSync(chrome2)) {
        return chrome2
    }
    let edge1 = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
    if (existsSync(edge1)) {
        return edge1
    }
    let chromeMac = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
    if (existsSync(chromeMac)) {
        return chromeMac
    }
    let edgeMac = '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
    if (existsSync(edgeMac)) {
        return edgeMac
    }
    // npm i -g puppeteer
    let cache = `${homedir()}/.cache/puppeteer/chrome/`
    let chrome = cache + globSync('**/chrome', { cwd: cache }).sort().at(-1)
    if (existsSync(chrome)) {
        return chrome
    }
    throw new Error('chrome executable not found!')
}


/**
 * @param {boolean} headless
 */
export async function startBrowser(headless) {
    console.log('startBrowser')
    let browser = await puppeteer.launch({
        headless: headless,
        userDataDir: fileURLToPath(new URL('../.git/userdata/', import.meta.url)),
        executablePath: getExecutablePath(),
        args: [
            '--no-first-run',
            '--no-zygote',
            '--disable-extensions',
            '--disable-infobars',
            '--disable-automation',
            '--no-default-browser-check',
            '--disable-device-orientation',
            '--disable-metrics-reporting',
            '--disable-logging',
            '--no-sandbox',
        ],
        ignoreDefaultArgs: [
            '--disable-extensions',
            '--enable-infobars',
            '--enable-features=NetworkService,NetworkServiceInProcess',
            '--enable-automation',
            'about:blank',
        ],
    })
    return browser
}

/**
 * @param {Page} page
 */
async function hideWebDriver(page) {
    await page.evaluateOnNewDocument(() => {
        let getFn = () => {
            return false
        }
        getFn.toString = () => 'function get webdriver() { [native code] }'
        Object.defineProperty(Navigator.prototype, 'webdriver', {
            get: getFn
        })
    })
}

/**
 * @param {Page} page
 */
async function interceprtNoUseRequest(page) {
    let blockRegexps = [
        /.png$/,
        /logs\.youdao\.com/,
    ]

    await page.setRequestInterception(true)
    page.on('request', async (request) => {
        let url = request.url()
        // console.log('interceprtNoUseRequest url:', url)
        if (blockRegexps.some(o => url.match(o))) {
            // console.log('block url:', url)
            request.abort()
            return
        }
        request.continue()
    })
}

const sleep = (/** @type {number} */ timeout) => new Promise((resolve) => setTimeout(resolve, timeout))

/**
 * @param {Browser} browser
 */
export async function startYoudaoPage(browser) {
    let page = await browser.newPage()
    await hideWebDriver(page)
    await interceprtNoUseRequest(page)
    await page.goto(`https://dict.youdao.com/`)
    await sleep(100)
    await page.waitForSelector('input')
    await sleep(100)
    return page
}

/**
 * @typedef {{ result: { msg: string; code: number; }; data: { entries: { explain: string; entry: string; }[]; query: string; language: string; type: string; }; }} DictResult
 */

/**
 * @param {Page} page
 * @param {string} word
 * @returns {Promise<DictResult>}
 */
export async function searchDict(page, word) {
    let ret = await page.evaluate(async (word) => {
        return await (await (fetch(`https://dict.youdao.com/suggest?num=5&ver=3.0&doctype=json&cache=false&le=en&q=${encodeURI(word)}`))).json()
    }, word)
    return ret
}

export class WatchDog {
    /**
     * @param {number} timeout
     * @param {()=>void} callback
     */
    constructor(timeout, callback) {
        this.timeout = timeout
        this.callback = callback
        this.feed()
    }

    feed() {
        clearTimeout(this.timer)
        this.timer = setTimeout(this.callback, this.timeout)
    }
}
